/**
 * Copyright (C) 2023 maminjie <canpool@163.com>
 * Copyright (C) 2017-2019 Uwe Kindler
 * SPDX-License-Identifier: LGPL-2.1
 **/

#include "QxDockFloatingDragPreview.h"
#include "QxDockAreaWidget.h"
#include "QxDockContainerWidget.h"
#include "QxDockManager.h"
#include "QxDockOverlay.h"
#include "QxDockWidget.h"
#include "QxDockAutoHideContainer.h"

#include <QApplication>
#include <QEvent>
#include <QKeyEvent>
#include <QPainter>

#include <iostream>

QX_BEGIN_NAMESPACE

/**
 * Private data class (pimpl)
 */
struct DockFloatingDragPreviewPrivate {
    DockFloatingDragPreview *_this;
    QWidget *Content;
    DockAreaWidget *ContentSourceArea = nullptr;
    QPoint DragStartMousePosition;
    DockManager *dockManager;
    DockContainerWidget *DropContainer = nullptr;
    qreal WindowOpacity;
    bool Hidden = false;
    QPixmap ContentPreviewPixmap;
    bool Canceled = false;

    /**
     * Private data constructor
     */
    DockFloatingDragPreviewPrivate(DockFloatingDragPreview *_public);
    void updateDropOverlays(const QPoint &GlobalPos);

    void setHidden(bool Value)
    {
        Hidden = Value;
        _this->update();
    }

    /**
     * Cancel dragging and emit the draggingCanceled event
     */
    void cancelDragging()
    {
        Canceled = true;
        Q_EMIT _this->draggingCanceled();
        dockManager->containerOverlay()->hideOverlay();
        dockManager->dockAreaOverlay()->hideOverlay();
        _this->close();
    }

    /**
     * Creates the real floating widget in case the mouse is released outside
     * outside of any drop area
     */
    void createFloatingWidget();

    /**
     * Returns true, if the content is floatable
     */
    bool isContentFloatable() const
    {
        DockWidget *dockWidget = qobject_cast<DockWidget *>(Content);
        if (dockWidget && dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) {
            return true;
        }

        DockAreaWidget *dockArea = qobject_cast<DockAreaWidget *>(Content);
        if (dockArea && dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) {
            return true;
        }

        return false;
    }
};
// struct LedArrayPanelPrivate

void DockFloatingDragPreviewPrivate::updateDropOverlays(const QPoint &GlobalPos)
{
    if (!_this->isVisible() || !dockManager) {
        return;
    }

    auto Containers = dockManager->dockContainers();
    DockContainerWidget *TopContainer = nullptr;
    for (auto ContainerWidget : Containers) {
        if (!ContainerWidget->isVisible()) {
            continue;
        }

        QPoint MappedPos = ContainerWidget->mapFromGlobal(GlobalPos);
        if (ContainerWidget->rect().contains(MappedPos)) {
            if (!TopContainer || ContainerWidget->isInFrontOf(TopContainer)) {
                TopContainer = ContainerWidget;
            }
        }
    }

    DropContainer = TopContainer;
    auto ContainerOverlay = dockManager->containerOverlay();
    auto DockAreaOverlay = dockManager->dockAreaOverlay();
    auto DockDropArea = DockAreaOverlay->dropAreaUnderCursor();
    auto ContainerDropArea = ContainerOverlay->dropAreaUnderCursor();

    if (!TopContainer) {
        ContainerOverlay->hideOverlay();
        DockAreaOverlay->hideOverlay();
        if (DockManager::testConfigFlag(DockManager::DragPreviewIsDynamic)) {
            setHidden(false);
        }
        return;
    }

    int VisibleDockAreas = TopContainer->visibleDockAreaCount();

    // Include the overlay widget we're dragging as a visible widget
    auto dockAreaWidget = qobject_cast<DockAreaWidget *>(Content);
    if (dockAreaWidget && dockAreaWidget->isAutoHide()) {
        VisibleDockAreas++;
    }

    ContainerOverlay->setAllowedAreas(VisibleDockAreas > 1 ? OuterDockAreas : AllDockAreas);
    auto dockArea = TopContainer->dockAreaAt(GlobalPos);
    if (dockArea && dockArea->isVisible() && VisibleDockAreas >= 0 && dockArea != ContentSourceArea) {
        DockAreaOverlay->enableDropPreview(true);
        DockAreaOverlay->setAllowedAreas((VisibleDockAreas == 1) ? NoDockWidgetArea : dockArea->allowedAreas());

        DockWidgetArea Area = DockAreaOverlay->showOverlay(dockArea);

        // A CenterDockWidgetArea for the dockAreaOverlay() indicates that
        // the mouse is in the title bar. If the ContainerArea is valid
        // then we ignore the dock area of the dockAreaOverlay() and disable
        // the drop preview
        if ((Area == CenterDockWidgetArea) && (ContainerDropArea != InvalidDockWidgetArea)) {
            DockAreaOverlay->enableDropPreview(false);
            ContainerOverlay->enableDropPreview(true);
        } else {
            ContainerOverlay->enableDropPreview(InvalidDockWidgetArea == Area);
        }
        ContainerOverlay->showOverlay(TopContainer);
    } else {
        DockAreaOverlay->hideOverlay();
        // If there is only one single visible dock area in a container, then
        // it does not make sense to show a dock overlay because the dock area
        // would be removed and inserted at the same position
        if (VisibleDockAreas == 1) {
            ContainerOverlay->hideOverlay();
        } else {
            ContainerOverlay->showOverlay(TopContainer);
        }

        if (dockArea == ContentSourceArea && InvalidDockWidgetArea == ContainerDropArea) {
            DropContainer = nullptr;
        }
    }

    if (DockManager::testConfigFlag(DockManager::DragPreviewIsDynamic)) {
        setHidden(DockDropArea != InvalidDockWidgetArea || ContainerDropArea != InvalidDockWidgetArea);
    }
}

DockFloatingDragPreviewPrivate::DockFloatingDragPreviewPrivate(DockFloatingDragPreview *_public) : _this(_public)
{
}

void DockFloatingDragPreviewPrivate::createFloatingWidget()
{
    DockWidget *dockWidget = qobject_cast<DockWidget *>(Content);
    DockAreaWidget *dockArea = qobject_cast<DockAreaWidget *>(Content);

    DockFloatingContainer *FloatingWidget = nullptr;

    if (dockWidget && dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) {
        FloatingWidget = new DockFloatingContainer(dockWidget);
    } else if (dockArea && dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) {
        FloatingWidget = new DockFloatingContainer(dockArea);
    }

    if (FloatingWidget) {
        FloatingWidget->setGeometry(_this->geometry());
        FloatingWidget->show();
        if (!DockManager::testConfigFlag(DockManager::DragPreviewHasWindowFrame)) {
            QApplication::processEvents();
            int FrameHeight = FloatingWidget->frameGeometry().height() - FloatingWidget->geometry().height();
            QRect FixedGeometry = _this->geometry();
            FixedGeometry.adjust(0, FrameHeight, 0, 0);
            FloatingWidget->setGeometry(FixedGeometry);
        }
    }
}

DockFloatingDragPreview::DockFloatingDragPreview(QWidget *Content, QWidget *parent)
    : QWidget(parent), d(new DockFloatingDragPreviewPrivate(this))
{
    d->Content = Content;
    setAttribute(Qt::WA_DeleteOnClose);
    if (DockManager::testConfigFlag(DockManager::DragPreviewHasWindowFrame)) {
        setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint);
    } else {
        setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
        setAttribute(Qt::WA_NoSystemBackground);
        setAttribute(Qt::WA_TranslucentBackground);
    }

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    auto Flags = windowFlags();
    Flags |= Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint;
    setWindowFlags(Flags);
#endif

    setWindowOpacity(0.6);

    // Create a static image of the widget that should get undocked
    // This is like some kind preview image like it is uses in drag and drop
    // operations
    if (DockManager::testConfigFlag(DockManager::DragPreviewShowsContentPixmap)) {
        d->ContentPreviewPixmap = QPixmap(Content->size());
        Content->render(&d->ContentPreviewPixmap);
    }

    connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
            SLOT(onApplicationStateChanged(Qt::ApplicationState)));

    // The only safe way to receive escape key presses is to install an event
    // filter for the application object
    qApp->installEventFilter(this);
}

DockFloatingDragPreview::DockFloatingDragPreview(DockWidget *Content)
    : DockFloatingDragPreview((QWidget *)Content, Content->dockManager())
{
    d->dockManager = Content->dockManager();
    if (Content->dockAreaWidget()->openDockWidgetsCount() == 1) {
        d->ContentSourceArea = Content->dockAreaWidget();
    }
    setWindowTitle(Content->windowTitle());
}

DockFloatingDragPreview::DockFloatingDragPreview(DockAreaWidget *Content)
    : DockFloatingDragPreview((QWidget *)Content, Content->dockManager())
{
    d->dockManager = Content->dockManager();
    d->ContentSourceArea = Content;
    setWindowTitle(Content->currentDockWidget()->windowTitle());
}

DockFloatingDragPreview::~DockFloatingDragPreview()
{
    delete d;
}

void DockFloatingDragPreview::moveFloating()
{
    int BorderSize = (frameSize().width() - size().width()) / 2;
    const QPoint moveToPos = QCursor::pos() - d->DragStartMousePosition - QPoint(BorderSize, 0);
    move(moveToPos);
    d->updateDropOverlays(QCursor::pos());
}

void DockFloatingDragPreview::startFloating(const QPoint &DragStartMousePos, const QSize &Size, eDragState DragState,
                                         QWidget *MouseEventHandler)
{
    Q_UNUSED(MouseEventHandler)
    Q_UNUSED(DragState)
    resize(Size);
    d->DragStartMousePosition = DragStartMousePos;
    moveFloating();
    show();
}

void DockFloatingDragPreview::finishDragging()
{
    QX_DOCK_PRINT("DockFloatingDragPreview::finishDragging");

    auto DockDropArea = d->dockManager->dockAreaOverlay()->visibleDropAreaUnderCursor();
    auto ContainerDropArea = d->dockManager->containerOverlay()->visibleDropAreaUnderCursor();
    bool ValidDropArea = (DockDropArea != InvalidDockWidgetArea) || (ContainerDropArea != InvalidDockWidgetArea);

    // Non floatable auto hide widgets should stay in its current auto hide
    // state if they are dragged into a floating window
    if (ValidDropArea || d->isContentFloatable()) {
        cleanupAutoHideContainerWidget();
    }

    if (!d->DropContainer) {
        d->createFloatingWidget();
    } else if (DockDropArea != InvalidDockWidgetArea) {
        d->DropContainer->dropWidget(d->Content, DockDropArea, d->DropContainer->dockAreaAt(QCursor::pos()));
    } else if (ContainerDropArea != InvalidDockWidgetArea) {
        // If there is only one single dock area, and we drop into the center
        // then we tabify the dropped widget into the only visible dock area
        if (d->DropContainer->visibleDockAreaCount() <= 1 && CenterDockWidgetArea == ContainerDropArea) {
            d->DropContainer->dropWidget(d->Content, ContainerDropArea, d->DropContainer->dockAreaAt(QCursor::pos()));
        } else {
            d->DropContainer->dropWidget(d->Content, ContainerDropArea, nullptr);
        }
    } else {
        d->createFloatingWidget();
    }

    this->close();
    d->dockManager->containerOverlay()->hideOverlay();
    d->dockManager->dockAreaOverlay()->hideOverlay();
}

void DockFloatingDragPreview::cleanupAutoHideContainerWidget()
{
    auto DroppedDockWidget = qobject_cast<DockWidget *>(d->Content);
    auto DroppedArea = qobject_cast<DockAreaWidget *>(d->Content);
    if (DroppedDockWidget && DroppedDockWidget->autoHideDockContainer()) {
        DroppedDockWidget->autoHideDockContainer()->cleanupAndDelete();
    }
    if (DroppedArea && DroppedArea->autoHideDockContainer()) {
        DroppedArea->autoHideDockContainer()->cleanupAndDelete();
    }
}

void DockFloatingDragPreview::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    if (d->Hidden) {
        return;
    }

    QPainter painter(this);
    if (DockManager::testConfigFlag(DockManager::DragPreviewShowsContentPixmap)) {
        painter.drawPixmap(QPoint(0, 0), d->ContentPreviewPixmap);
    }

    // If we do not have a window frame then we paint a QRubberBand like
    // frameless window
    if (!DockManager::testConfigFlag(DockManager::DragPreviewHasWindowFrame)) {
        QColor Color = palette().color(QPalette::Active, QPalette::Highlight);
        QPen Pen = painter.pen();
        Pen.setColor(Color.darker(120));
        Pen.setStyle(Qt::SolidLine);
        Pen.setWidth(1);
        Pen.setCosmetic(true);
        painter.setPen(Pen);
        Color = Color.lighter(130);
        Color.setAlpha(64);
        painter.setBrush(Color);
        painter.drawRect(rect().adjusted(0, 0, -1, -1));
    }
}

void DockFloatingDragPreview::onApplicationStateChanged(Qt::ApplicationState state)
{
    if (state != Qt::ApplicationActive) {
        disconnect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this,
                   SLOT(onApplicationStateChanged(Qt::ApplicationState)));
        d->cancelDragging();
    }
}

bool DockFloatingDragPreview::eventFilter(QObject *watched, QEvent *event)
{
    Q_UNUSED(watched);
    if (!d->Canceled && event->type() == QEvent::KeyPress) {
        QKeyEvent *e = static_cast<QKeyEvent *>(event);
        if (e->key() == Qt::Key_Escape) {
            watched->removeEventFilter(this);
            d->cancelDragging();
        }
    }

    return false;
}

QX_END_NAMESPACE
